package com.seng.resourcetycoon.services.impl;

import com.seng.resourcetycoon.core.enums.DifficultyLevel;
import com.seng.resourcetycoon.core.enums.ResourceType;
import com.seng.resourcetycoon.event.ResourceCountChangeEvent;
import com.seng.resourcetycoon.gui.models.LoadBearer;
import com.seng.resourcetycoon.event.OnGameFailListener;
import com.seng.resourcetycoon.event.OnGameWinListener;
import com.seng.resourcetycoon.gui.views.LoadBearerItemView;
import com.seng.resourcetycoon.services.LoadBearerService;
import com.seng.resourcetycoon.services.ResourceInventory;
import com.seng.resourcetycoon.manager.GameEnvironment;
import com.seng.resourcetycoon.services.GamePlayService;
import com.seng.resourcetycoon.util.TowerAttributeGenerator;
import com.seng.resourcetycoon.util.Util;
import javafx.animation.AnimationTimer;
import javafx.beans.value.ChangeListener;
import javafx.beans.value.ObservableValue;

import java.util.*;
import java.util.concurrent.CopyOnWriteArrayList;
import java.util.concurrent.ThreadLocalRandom;
import java.util.stream.Collectors;

public class LoadBearerServiceImpl implements LoadBearerService, ChangeListener<ResourceCountChangeEvent> {

    public static final int MIN_CAPACITY = 40;
    public static final int MAX_EASY_CAPACITY = 80;
    public static final int MAX_MEDIUM_CAPACITY = 100;
    public static final int MAX_HARD_CAPACITY = 250;

    public static final Map<DifficultyLevel, Integer> MAX_CAPACITIES = new HashMap<>();

    static {
        MAX_CAPACITIES.put(DifficultyLevel.EASY, MAX_EASY_CAPACITY);
        MAX_CAPACITIES.put(DifficultyLevel.MEDIUM, MAX_MEDIUM_CAPACITY);
        MAX_CAPACITIES.put(DifficultyLevel.HARD, MAX_HARD_CAPACITY);
    }

    private List<LoadBearer> loadBearers;
    private List<LoadBearerItemView> loadBearerItemViews;
    private static final Random random = new Random();
    private final TowerAttributeGenerator towerAttributeGenerator;

    // 用于保存所有注册的回调监听器
    private final List<OnGameWinListener> gameWinListeners = new ArrayList<>();
    private final List<OnGameFailListener> gameFailListeners = new ArrayList<>();
    private AnimationTimer animationTimer = null;
    private boolean gameFail = false;


    public LoadBearerServiceImpl() {
        // Initialize the cart list to ensure proper functioning even if not explicitly initialized elsewhere
        loadBearers = new ArrayList<>();

        // Initialize the cart item view list
        loadBearerItemViews = new CopyOnWriteArrayList<>();

        // Instantiate a TowerAttributeGenerator to generate attributes for carts or their items
        towerAttributeGenerator = new TowerAttributeGenerator();

        // Set the number of carts, here hardcoded for illustration; in practice, this might be read from a configuration file or external parameter
        int numberOfCarts = 3;

        // Determine the cart generation strategy: random generation or default to an empty list if 'useRandomGeneration' is not enabled

        // Assign the generated carts to the service, overriding any previous initialization (even if it was just the default empty list)
        loadBearers = generateCartsRandomly(numberOfCarts);


        // Register the current service as a listener for resource count changes, enabling a response when resources fluctuate
        ResourceInventory.getInstance().addResourceCountChangeListener(this);
    }

    /**
     * Generates a specified number of carts with random attributes, ensuring uniqueness among resource types.
     * This method is particularly useful for initializing the system with a diverse set of carts.
     *
     * @param numberOfCarts The number of carts to generate.
     * @return A list of randomly generated Cart objects.
     */
    private List<LoadBearer> generateCartsRandomly(int numberOfCarts) {
        List<LoadBearer> loadBearers = new ArrayList<>(); // Prepare a list to hold the generated carts

        // Initialize a set to track the resource types already used to avoid duplicates across carts
        Set<ResourceType> generatedResourceTypes = new HashSet<>();

        // Loop to create the requested number of carts
        for (int i = 0; i < numberOfCarts; i++) {
            // Invoke a helper method (not shown) to create a single cart with random attributes,
            // ensuring that its ResourceType hasn't been used before in this batch.
            // The 'generatedResourceTypes' set prevents duplication.
            loadBearers.add(randomCreateCart(generatedResourceTypes));
        }

        // Return the list of carts after all have been successfully generated
        return loadBearers;
    }

    @Override
    public void randomlyIncreaseResource() {
        LoadBearer randomLoadBearer = Util.improvedRandomElementOrDefault(loadBearers, new LoadBearer(null));

        Map<ResourceType, Integer> resources = randomLoadBearer.getRequiredResources();
        if (resources == null || resources.isEmpty()) {
            System.out.println("No resources to increase.");
            return;
        }

        Optional<Map.Entry<ResourceType, Integer>> randomEntry = resources.entrySet().stream()
                .skip(ThreadLocalRandom.current().nextInt(resources.size()))
                .findFirst();

        randomEntry.ifPresent(entry -> {
            ResourceType randomKey = entry.getKey();
            int currentValue = entry.getValue();
            resources.put(randomKey, currentValue + 30);
            loadBearerItemViews
                    .forEach(cartItemView -> cartItemView.refreshRequirementByCart(randomLoadBearer));
        });
    }

    // 注册游戏胜利的监听器
    public void registerOnGameWinListener(OnGameWinListener listener) {
        if (listener != null && !gameWinListeners.contains(listener)) {
            gameWinListeners.add(listener);
        }
    }

    // 注销游戏胜利的监听器
    public void unregisterOnGameWinListener(OnGameWinListener listener) {
        gameWinListeners.remove(listener);
    }

    public void registerOnGameFailListener(OnGameFailListener listener) {
        if (listener != null && !gameFailListeners.contains(listener)) {
            gameFailListeners.add(listener);
        }
    }

    public void unregisterOnGameFailListener(OnGameFailListener listener) {
        gameFailListeners.remove(listener);
    }

    @Override
    public List<LoadBearerItemView> getCartItemViews() {
        return loadBearerItemViews;
    }


    /**
     * Creates and returns a randomly generated {@link LoadBearer} instance with a unique resource requirement.
     * This method generates a distinct {@link ResourceType} by calling {@link TowerAttributeGenerator#generateUniqueResourceType(Set)},
     * adds the generated resource type to a set of already used types,
     * assigns a random quantity of the resource between 1 and 3 inclusively,
     * and finally constructs a {@link LoadBearer} object using the resource type and its quantity.
     *
     * @return A newly created {@link LoadBearer} instance with a unique resource requirement.
     */
    private LoadBearer randomCreateCart(Set<ResourceType> generatedResourceTypes) {

        // Generate a unique resource type
        ResourceType requiredResourceType = towerAttributeGenerator.generateUniqueResourceType(generatedResourceTypes);
        generatedResourceTypes.add(requiredResourceType); // Add the generated resource type to the set

        // Generate a random quantity of the resource
        int requiredQuantity = generateCapacity(GameEnvironment.getInstance().getDifficultyLevel());

        // Create a map representing the resource requirements
        Map<ResourceType, Integer> requiredResources = new HashMap<>();
        requiredResources.put(requiredResourceType, requiredQuantity);

        // Construct and return a new Cart instance with the specified resource requirements
        return new LoadBearer(requiredResources);
    }

    /**
     * Generates a random capacity value based on the given difficulty level and the current game round.
     * The capacity is adjusted according to the difficulty and increases with each game round.
     *
     * @param difficulty the DifficultyLevel of the game, which determines the minimum and maximum capacity range.
     * @return the generated capacity, calculated considering the difficulty and the game's progression.
     */
    public static int generateCapacity(DifficultyLevel difficulty) {
        int minCapacity = MIN_CAPACITY;
        int maxCapacity = MAX_CAPACITIES.get(difficulty);

        int baseCapacity = random.nextInt(maxCapacity - minCapacity + 1) + minCapacity;
        if (difficulty == DifficultyLevel.HARD) {
            baseCapacity += random.nextInt((maxCapacity - baseCapacity) / 2 + 20);
        } else if (difficulty == DifficultyLevel.MEDIUM) {
            baseCapacity += random.nextInt((maxCapacity - baseCapacity) / 2 + 10);
        }

        baseCapacity += GameEnvironment.getInstance().getGameRound() * 2;

        return baseCapacity;
    }

    public List<LoadBearer> getUnfilledCartsForResourceType(ResourceType resourceType) {
        if (resourceType == null) {
            return new ArrayList<>();
        }
        return loadBearers.stream().filter(loadBearer -> !loadBearer.isFullForResourceType(resourceType)) // 过滤出来满的
                .collect(Collectors.toList());
    }

    /**
     * Distributes the specified amount ({@code delta}) of a given {@link ResourceType} among
     * unfilled carts. The method iterates through the carts and calls
     * {@link LoadBearer#tryAddToCart(ResourceType, int)} to add resources until either all resources are
     * distributed or no more space is available in the carts.
     *
     * <p>This method also checks for potential overflow issues by ensuring that the filled quantity
     * returned from {@code addResource} does not exceed the available quantity. If an overflow occurs,
     * an {@link IllegalStateException} is thrown.
     *
     * <p>After the distribution, the game win condition is checked using
     * {@link #checkAndNotifyGameWin()}, and the UI is refreshed by calling
     * {@link LoadBearerItemView#refreshLoadedCapacities()} on each view.
     *
     * @param resourceType the type of resource to be distributed
     * @param delta        the number of resources to distribute
     * @throws IllegalStateException if the filled quantity exceeds the available quantity during
     *                               resource allocation
     */
    private void distributeResource(ResourceType resourceType, int delta) {
        int quantity = delta;
        List<LoadBearer> unfilledLoadBearers = getUnfilledCartsForResourceType(resourceType);
        for (LoadBearer loadBearer : unfilledLoadBearers) {
            int filledQuantity = loadBearer.tryAddToCart(resourceType, delta);
            if (filledQuantity > quantity) {
                // 这里可以根据实际情况记录日志或者抛出异常
                throw new IllegalStateException("Filled quantity exceeds available quantity.");
            }
            quantity -= filledQuantity;
            if (quantity <= 0) break;
        }

        checkAndNotifyGameWin();

        // refresh UI
        for (LoadBearerItemView loadBearerItemView : loadBearerItemViews) {
            loadBearerItemView.refreshLoadedCapacities();
        }
    }

    // 检查并通知游戏胜利
    private boolean checkAndNotifyGameWin() {
        try {
            if (loadBearers != null && !loadBearers.isEmpty() && loadBearers.stream().allMatch(LoadBearer::allRequirementsMet)) {
                for (OnGameWinListener listener : gameWinListeners) {
                    listener.onGameWin();
                }
                return true;
            }
        } catch (Exception e) {
            e.printStackTrace();
        }
        return false;
    }

    @Override
    public void initialize(GamePlayService gamePlayService) {
        // Iterate through the cart list, creating a view for each cart and binding data to it
        for (LoadBearer loadBearer : loadBearers) {
            LoadBearerItemView loadBearerItemView = new LoadBearerItemView();
            loadBearerItemViews.add(loadBearerItemView); // Add the view to the views list
            loadBearerItemView.bindData(loadBearer); // Bind the cart data to the view
        }

        registerOnGameFailListener(gamePlayService);
        registerOnGameWinListener(gamePlayService);

        animationTimer = new AnimationTimer() {
            @Override
            public void handle(long now) {
                if (gameFail) {
                    animationTimer.stop();
                    return;
                }
                for (int i = 0; i < loadBearerItemViews.size(); i++) {
                    LoadBearerItemView loadBearerItemView = loadBearerItemViews.get(i);
                    if (!loadBearerItemView.refreshPosition() && !loadBearers.get(i).allRequirementsMet()) {
                        for (OnGameFailListener listener : gameFailListeners) {
                            listener.onGameFail();
                            gameFail = true;
                        }
                        break;
                    }
                }
            }
        };
        animationTimer.start();
    }

    @Override
    public void begin() {
        animationTimer.start();
    }

    @Override
    public void suspend() {
        animationTimer.stop();
    }

    @Override
    public void continueProcessing() {
        animationTimer.start();
    }

    @Override
    public void cleanup(GamePlayService gamePlayService) {
        if (animationTimer != null) {
            animationTimer.stop();
        }
        ResourceInventory.getInstance().removeResourceCountChangeListener(this);
        unregisterOnGameFailListener(gamePlayService);
        unregisterOnGameWinListener(gamePlayService);
        loadBearerItemViews.clear();
        loadBearers.clear();
    }

    @Override
    public void changed(ObservableValue<? extends ResourceCountChangeEvent> observableValue, ResourceCountChangeEvent resourceCountChangeEvent, ResourceCountChangeEvent t1) {
        distributeResource(t1.getResourceType(), t1.getDelta());
    }
}
